package cn.remexs.doris.geo;

import org.apache.commons.lang3.ArrayUtils;
import org.geotools.geojson.geom.GeometryJSON;
import org.geotools.geometry.jts.GeometryBuilder;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.text.DecimalFormat;

public class GeoUtils {


    public static DecimalFormat df = new DecimalFormat("0.00000000000");
    /**
     * 地球半径（米）
     */
    public final static double radius = 6378245.0;
    /**
     * # 扁率
     */
    public final static double ee = 0.00669342162296594323;
    /**
     * 政通 墨卡托坐标偏移
     */
    public static double ztLonOffset = -0.013283571;
    public static double ztLatOffset = +0.15866781;


    /**
     * @param lon 经度
     * @param lat 纬度
     * @return
     * @Description WGS84 to 火星坐标系 (GCJ-02)
     */
    public static double[] wgs84ToGcj02(double lon, double lat) {
        if (outOfChina(lat, lon)) {
            return null;
        }
        double dLat = transformLat(lon - 105.0, lat - 35.0);
        double dLon = transformLon(lon - 105.0, lat - 35.0);
        double radLat = lat / 180.0 * Math.PI;
        double magic = Math.sin(radLat);
        magic = 1 - ee * magic * magic;
        double sqrtMagic = Math.sqrt(magic);
        dLat = (dLat * 180.0) / ((radius * (1 - ee)) / (magic * sqrtMagic) * Math.PI);
        dLon = (dLon * 180.0) / (radius / sqrtMagic * Math.cos(radLat) * Math.PI);
        double mgLat = lat + dLat;
        double mgLon = lon + dLon;
        return new double[]{mgLon, mgLat};
    }

    /**
     * @param lon
     * @param lat
     * @return
     * @Description 火星坐标系 (GCJ-02) to WGS84
     */
    public static double[] gcj02ToWgs84(double lon, double lat) {
        double[] point = transform(lat, lon);
        double lontitude = lon * 2 - point[1];
        double latitude = lat * 2 - point[0];
        return new double[]{lontitude, latitude};
    }

    /**
     * @param gg_lon
     * @param gg_lat
     * @return
     * @Description 火星坐标系 (GCJ-02) to 百度坐标系 (BD-09)
     */
    public static double[] gcj02ToBd09(double gg_lon, double gg_lat) {
        double x = gg_lon, y = gg_lat;
        double z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * (Math.PI * 3000.0 / 180.0));
        double theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * (Math.PI * 3000.0 / 180.0));
        double bd_lon = z * Math.cos(theta) + 0.0065;
        double bd_lat = z * Math.sin(theta) + 0.006;
        return new double[]{bd_lon, bd_lat};
    }

    /**
     * @param bd_lon
     * @param bd_lat
     * @return
     * @Description 百度坐标系 (BD-09) to 火星坐标系 (GCJ-02)
     */
    public static double[] bd09ToGcj02(double bd_lon, double bd_lat) {
        double x = bd_lon - 0.0065, y = bd_lat - 0.006;
        double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * (Math.PI * 3000.0 / 180.0));
        double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * (Math.PI * 3000.0 / 180.0));
        double gg_lon = z * Math.cos(theta);
        double gg_lat = z * Math.sin(theta);
        return new double[]{gg_lon, gg_lat};
    }

    /**
     * @param bd_lat
     * @param bd_lon
     * @return
     * @Description 百度坐标系 (BD-09) to WGS84
     */
    public static double[] bd09ToWgs84(double bd_lon, double bd_lat) {

        double[] gcj02 = bd09ToGcj02(bd_lon, bd_lat);
        double[] map84 = gcj02ToWgs84(gcj02[0], gcj02[1]);
        return map84;

    }

    /**
     * @param lat
     * @param lon
     * @return
     * @Description 判断是否在中国范围内
     */
    public static boolean outOfChina(double lat, double lon) {
        if (lon < 72.004 || lon > 137.8347)
            return true;
        if (lat < 0.8293 || lat > 55.8271)
            return true;
        return false;
    }

    /**
     * @param lat
     * @param lon
     * @return
     * @Description transform
     */
    private static double[] transform(double lat, double lon) {
        if (outOfChina(lat, lon)) {
            return new double[]{lat, lon};
        }
        double dLat = transformLat(lon - 105.0, lat - 35.0);
        double dLon = transformLon(lon - 105.0, lat - 35.0);
        double radLat = lat / 180.0 * Math.PI;
        double magic = Math.sin(radLat);
        magic = 1 - ee * magic * magic;
        double sqrtMagic = Math.sqrt(magic);
        dLat = (dLat * 180.0) / ((radius * (1 - ee)) / (magic * sqrtMagic) * Math.PI);
        dLon = (dLon * 180.0) / (radius / sqrtMagic * Math.cos(radLat) * Math.PI);
        double mgLat = lat + dLat;
        double mgLon = lon + dLon;
        return new double[]{mgLat, mgLon};
    }

    /**
     * @param x
     * @param y
     * @return
     * @Description transformLat
     */
    private static double transformLat(double x, double y) {
        double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(y * Math.PI) + 40.0 * Math.sin(y / 3.0 * Math.PI)) * 2.0 / 3.0;
        ret += (160.0 * Math.sin(y / 12.0 * Math.PI) + 320 * Math.sin(y * Math.PI / 30.0)) * 2.0 / 3.0;
        return ret;
    }

    /**
     * @param x
     * @param y
     * @return
     * @Description transformLon
     */
    public static double transformLon(double x, double y) {
        double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(x * Math.PI) + 40.0 * Math.sin(x / 3.0 * Math.PI)) * 2.0 / 3.0;
        ret += (150.0 * Math.sin(x / 12.0 * Math.PI) + 300.0 * Math.sin(x / 30.0 * Math.PI)) * 2.0 / 3.0;
        return ret;
    }

    /**
     * 墨卡托坐标转经纬度
     *
     * @param lon
     * @param lat
     * @param offset 政通坐标偏移
     * @return
     */
    public static double[] mercatorToLonLat(double lon, double lat, boolean offset) {
        lon = (lon / 20037508.34) * 180.0;
        lat = (lat / 20037508.34) * 180.0;
        lat = (180.0 / Math.PI) * (2.0 * Math.atan(Math.exp((lat * Math.PI / 180.0))) - (Math.PI / 2.0));
        return new double[]{lon + ztLonOffset, lat + ztLatOffset};
    }

    /**
     * 墨卡托坐标转经纬度
     *
     * @param lon
     * @param lat
     * @return
     */
    public static double[] mercatorToLonLat(double lon, double lat) {
        lon = (lon / 20037508.34) * 180.0;
        lat = (lat / 20037508.34) * 180.0;
        lat = (180.0 / Math.PI) * (2.0 * Math.atan(Math.exp((lat * Math.PI / 180.0))) - (Math.PI / 2.0));
        return new double[]{lon, lat};
    }


    /**
     * WKT 格式转化为 GeoJson
     *
     * @Param: 传入WKT形式的字符串
     */
    public static String wktToJson(String wkt) {
        WKTReader reader = new WKTReader();
        Geometry geometry = null;
        try {
            geometry = reader.read(wkt);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        StringWriter writer = new StringWriter();
        GeometryJSON g = new GeometryJSON(15);
        try {
            g.write(geometry, writer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return writer.toString();
    }

    /**
     * GeoJson 格式转化为 WKT
     *
     * @Param: 传入GeoJson形式的字符串
     */
    public static String jsonToWkt(String geoJson) {
        GeometryJSON json = new GeometryJSON(15);
        Reader reader = new StringReader(geoJson);
        Geometry geometry = null;
        try {
            geometry = json.read(reader);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return geometry.toText();
    }

    /**
     * GeoJson 格式转化为 WKT
     *
     * @Param: 传入GeoJson形式的字符串
     */
    public static String buildJson(double lon, double lat) {
        return wktToJson(buildWkt(lon, lat));
    }

    /**
     * GeoJson 格式转化为 WKT
     *
     * @Param: 传入GeoJson形式的字符串
     */
    public static String buildWkt(double lon, double lat) {
        GeometryBuilder builder = new GeometryBuilder(new GeometryFactory(new PrecisionModel(), 4326));
        return builder.point(lon, lat).toText();
    }

    /**
     * wkt坐标集转坐标系转其他坐标系
     *
     * @return
     */
    public static Geometry wktToGeometry(String wkt) {
        WKTReader reader = new WKTReader();
        Geometry geometry = null;
        try {
            geometry = reader.read(wkt);
        } catch (ParseException e) {
            return null;
        }
        return geometry;
    }

    /**
     * wkt坐标集转坐标系转其他坐标系
     *
     * @return
     */
    public static Geometry jsonToGeometry(String geoJson) {
        GeometryJSON json = new GeometryJSON(15);
        Reader reader = new StringReader(geoJson);
        Geometry geometry = null;
        try {
            geometry = json.read(reader);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return geometry;
    }

    /**
     * wkt坐标集转坐标系转其他坐标系
     *
     * @return
     */
    public static String transWkt(String source, String target, String wkt, boolean is3d) {
        WKTReader reader = new WKTReader();
        Geometry geometry = null;
        try {
            geometry = reader.read(wkt);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return geometryToWkt(source, target, geometry, is3d);
    }

    /**
     * wkt坐标集转坐标系转其他坐标系
     *
     * @return
     */
    public static String transJson(String source, String target, String geoJson, boolean is3d) {
        GeometryJSON json = new GeometryJSON(15);
        Reader reader = new StringReader(geoJson);
        Geometry geometry = null;
        try {
            geometry = json.read(reader);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return geometryToWkt(source, target, geometry, is3d);
    }

    public static String geometryToWkt(String source, String target, Geometry geom, boolean is3d) {
        if (geom == null) {
            return null;
        }
        String type = geom.getGeometryType().toUpperCase();
        switch (type) {
            case "POINT": {
                Coordinate coord = geom.getCoordinate();
                return "POINT(" + formatCoordinate(source, target, coord, is3d) + ")";
            }
            case "MULTIPOINT": {
                return coordinateToWkt(source, target, "MULTIPOINT(", geom.getCoordinates(), ")", is3d);
            }
            case "LINESTRING": {
                return coordinateToWkt(source, target, "LINESTRING(", geom.getCoordinates(), ")", is3d);
            }
            case "POLYGON": {
                return coordinateToWkt(source, target, "POLYGON((", geom.getCoordinates(), "))", is3d);
            }
            case "MULTILINESTRING": {
                StringBuffer sb = new StringBuffer();
                sb.append("MULTILINESTRING(");
                sb.append(multiGeometryToWkt(false, source, target, geom, is3d));
                sb.append(")");
                return sb.toString();
            }
            case "MULTIPOLYGON": {
                StringBuffer sb = new StringBuffer();
                sb.append("MULTIPOLYGON(");
                sb.append(multiGeometryToWkt(true, source, target, geom, is3d));
                sb.append(")");
                return sb.toString();
            }
            case "GEOMETRYCOLLECTION": {
                StringBuffer sb = new StringBuffer();
                int num = geom.getNumGeometries();
                sb.append("GEOMETRYCOLLECTION(");
                for (int j = 0; j < num; j++) {
                    Geometry element = geom.getGeometryN(j);
                    String subType = element.getGeometryType().toUpperCase();
                    if ("POINT".equals(subType)) {
                        sb.append("POINT(" + formatCoordinate(source, target, element.getCoordinate(), is3d) + ")");
                    } else if ("LINESTRING".equals(subType)) {
                        sb.append(coordinateToWkt(source, target, "LINESTRING(", element.getCoordinates(), ")", is3d));
                    } else if ("POLYGON".equals(subType)) {
                        sb.append(coordinateToWkt(source, target, "POLYGON((", element.getCoordinates(), "))", is3d));
                    }
                    if (j < num - 1) {
                        sb.append(",");
                    }
                }
                sb.append(")");
                return sb.toString();
            }
        }
        return null;
    }

    public static String multiGeometryToWkt(boolean isPolygon, String source, String target, Geometry geom, boolean is3d) {
        StringBuffer sb = new StringBuffer();
        int num = geom.getNumGeometries();
        for (int i = 0; i < num; i++) {
            Geometry g = geom.getGeometryN(i);
            Coordinate[] coordinates = g.getCoordinates();
            if (isPolygon && !coordinates[0].equals(coordinates[coordinates.length - 1])) {
                coordinates = ArrayUtils.add(coordinates, coordinates[0]);
            }
            sb.append(coordinateToWkt(source, target, "((", coordinates, "))", is3d));
            if (i < num - 1) {
                sb.append(",");
            }
        }
        return sb.toString();
    }

    public static String coordinateToWkt(String source, String target, String prefix, Coordinate[] coordinates, String suffix, boolean is3d) {
        StringBuffer sb = new StringBuffer();
        sb.append(prefix);
        boolean append = false;
        for (Coordinate coordinate : coordinates) {
            if (append) {
                sb.append(",");
            }
            sb.append(formatCoordinate(source, target, coordinate, is3d));
            append = true;
        }
        sb.append(suffix);
        return sb.toString();
    }

    private static String formatCoordinate(String source, String target, Coordinate coordinate, boolean is3d) {
        Coordinate coord = transCoordinate(source, target, coordinate);
        if (null == coord) return null;
        if (!is3d) {
            return df.format(coord.x) + " " + df.format(coord.y);
        } else {
            if (Double.isNaN(coord.z)) {
                coord.z = 0;
            }
            return df.format(coord.x) + " " + df.format(coordinate.y) + " " + (coordinate.z != 0 ? df.format(coordinate.z) : "0");
        }

    }

    public static Coordinate transCoordinate(String source, String target, Coordinate coordinate) {
        double[] gcj02 = null;
        double[] bd09 = null;
        double[] wgs84 = null;
        if ("wgs84".equals(source)) {
            switch (target) {
                case "bd09":
                    gcj02 = GeoUtils.wgs84ToGcj02(coordinate.getX(), coordinate.getY());
                    bd09 = GeoUtils.gcj02ToBd09(gcj02[0], gcj02[1]);
                    return new Coordinate(bd09[0], bd09[1]);
                case "gcj02":
                    gcj02 = GeoUtils.wgs84ToGcj02(coordinate.getX(), coordinate.getY());
                    return new Coordinate(gcj02[0], gcj02[1]);
                default:
                    return coordinate;
            }
        } else if ("gcj02".equals(source)) {
            switch (target) {
                case "bd09":
                    bd09 = GeoUtils.gcj02ToBd09(coordinate.getX(), coordinate.getY());
                    return new Coordinate(bd09[0], bd09[1]);
                case "wgs84":
                    wgs84 = GeoUtils.gcj02ToWgs84(coordinate.getX(), coordinate.getY());
                    return new Coordinate(wgs84[0], wgs84[1]);
                default:
                    return coordinate;
            }
        } else if ("bd09".equals(source)) {
            switch (target) {
                case "gcj02":
                    gcj02 = GeoUtils.bd09ToGcj02(coordinate.getX(), coordinate.getY());
                    return new Coordinate(gcj02[0], gcj02[1]);
                case "wgs84":
                    wgs84 = GeoUtils.bd09ToWgs84(coordinate.getX(), coordinate.getY());
                    return new Coordinate(wgs84[0], wgs84[1]);
                default:
                    return coordinate;
            }
        }
        return null;
    }

}




